<!DOCTYPE html>
<html lang="zh-cn">
	<head>
		<meta charset="UTF-8" />
		<title>Mock 接口数据生成器</title>
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		<!-- 引入 Bootstrap 5 CDN -->
		<link
			href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
			rel="stylesheet"
		/>
		<style>
			body {
				margin: 30px;
			}
			#result {
				white-space: pre-wrap;
				word-break: break-all;
				background-color: #f8f8f8;
				padding: 10px;
				border: 1px solid #ccc;
			}
			@media (max-width: 576px) {
				#result,
				textarea,
				input,
				select {
					width: 100% !important;
					min-width: 0 !important;
				}
			}
		</style>
	</head>
	<body>
		<div class="container">
			<h2 class="my-3">接口数据生成器</h2>
			<div class="mb-3">
				<label class="form-label fw-bold">自动解析 curl 请求：</label>
				<div class="input-group">
					<textarea
						id="curlInput"
						class="form-control"
						rows="4"
						placeholder="粘贴 curl 命令自动解析"
					></textarea>
					<button type="button" class="btn btn-primary" onclick="parseCurl()">
						解析
					</button>
				</div>
			</div>
			<form id="mockForm">
				<div class="row g-3">
					<div class="col-md-6">
						<label class="form-label">接口URL:</label>
						<input type="text" name="url" class="form-control" required />
					</div>
					<div class="col-md-3">
						<label class="form-label">请求类型:</label>
						<select name="method" class="form-select">
							<option value="GET">GET</option>
							<option value="POST">POST</option>
							<option value="PUT">PUT</option>
							<option value="DELETE">DELETE</option>
						</select>
					</div>
					<div class="col-md-3">
						<label class="form-label">请求次数:</label>
						<input
							type="number"
							name="count"
							min="1"
							value="1"
							class="form-control"
							required
						/>
					</div>
				</div>
				<div class="mb-3 mt-3">
					<label class="form-label">Header：</label>
					<div class="form-check form-switch mb-2">
						<input
							class="form-check-input"
							type="checkbox"
							id="headerModeSwitch"
							onchange="toggleHeaderMode()"
						/>
						<label class="form-check-label" for="headerModeSwitch"
							>切换为表单模式</label
						>
					</div>
					<div id="header-json-area">
						<textarea
							name="headers"
							class="form-control"
							rows="3"
							placeholder='{"Content-Type":"application/json"}'
						></textarea>
					</div>
					<div
						id="header-table-area"
						style="display: none; max-height: 200px; overflow: auto"
					>
						<table
							class="table table-bordered align-middle mb-0"
							id="headerTable"
						>
							<thead class="table-light">
								<tr>
									<th>Header名</th>
									<th>Header值</th>
									<th>操作</th>
								</tr>
							</thead>
							<tbody>
								<!-- header行会动态添加 -->
							</tbody>
						</table>
						<button
							type="button"
							class="btn btn-outline-secondary btn-sm mt-2"
							onclick="addHeaderRow()"
						>
							添加Header
						</button>
					</div>
				</div>
				<h5 class="mt-4">接口参数</h5>
				<div class="table-responsive" style="max-height: 200px; overflow: auto">
					<table class="table table-bordered align-middle" id="paramsTable">
						<thead class="table-light">
							<tr>
								<th>参数名</th>
								<th>参数值</th>
								<th>生成方式</th>
								<th>操作</th>
							</tr>
						</thead>
						<tbody>
							<!-- 参数行会动态添加 -->
						</tbody>
					</table>
				</div>
				<button
					type="button"
					class="btn btn-outline-secondary mb-3"
					onclick="addParamRow()"
				>
					添加参数
				</button>
				<div class="mb-3">
					<button
						type="button"
						class="btn btn-success me-2"
						onclick="generateMockData()"
					>
						生成 cURL 数据
					</button>
					<button
						type="button"
						class="btn btn-primary"
						onclick="executeRequest()"
					>
						执行请求
					</button>
				</div>
			</form>
			<h5 class="mt-4">生成结果</h5>
			<pre id="result" class="rounded"></pre>
		</div>
		<!-- Bootstrap JS (可选) -->
		<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
		<script>
			function addParamRow(name = '', value = '', genType = 'manual') {
				const tbody = document.querySelector('#paramsTable tbody');
				const row = document.createElement('tr');
				row.innerHTML = `
            <td><input type="text" class="form-control param-name" required value="${name}"></td>
            <td><input type="text" class="form-control param-value" value="${value}"></td>
            <td>
                <select class="form-select param-gen">
                    <option value="manual">手动输入</option>
                    <option value="random">随机字符串</option>
                    <option value="increment">递增序列号</option>
                    <option value="mobile">手机号</option>
                    <option value="email">电子邮箱</option>
                </select>
            </td>
            <td><button type="button" class="btn btn-sm btn-danger" onclick="this.parentElement.parentElement.remove();saveFormToLocalStorage();">删除</button></td>
        `;
				tbody.appendChild(row);
				row.querySelector('.param-gen').value = genType;
				// 监听参数变化自动保存
				row
					.querySelector('.param-name')
					.addEventListener('input', saveFormToLocalStorage);
				row
					.querySelector('.param-value')
					.addEventListener('input', saveFormToLocalStorage);
				row
					.querySelector('.param-gen')
					.addEventListener('change', saveFormToLocalStorage);
			}

			function addHeaderRow(key = '', value = '') {
				const tbody = document.querySelector('#headerTable tbody');
				const row = document.createElement('tr');
				row.innerHTML = `
					<td><input type="text" class="form-control header-key" value="${key}"></td>
					<td><input type="text" class="form-control header-value" value="${value}"></td>
					<td><button type="button" class="btn btn-sm btn-danger" onclick="this.parentElement.parentElement.remove();saveFormToLocalStorage();">删除</button></td>
				`;
				tbody.appendChild(row);
				row
					.querySelector('.header-key')
					.addEventListener('input', saveFormToLocalStorage);
				row
					.querySelector('.header-value')
					.addEventListener('input', saveFormToLocalStorage);
			}

			function getHeaders() {
				const isFormMode = document.getElementById('headerModeSwitch').checked;
				if (!isFormMode) {
					try {
						const headerStr = document.querySelector(
							'textarea[name="headers"]'
						).value;
						return headerStr ? JSON.parse(headerStr) : {};
					} catch (e) {
						alert('Header 格式错误，请输入合法 JSON');
						throw e;
					}
				} else {
					const headers = {};
					document.querySelectorAll('#headerTable tbody tr').forEach((row) => {
						const key = row.querySelector('.header-key').value.trim();
						const value = row.querySelector('.header-value').value;
						if (key) headers[key] = value;
					});
					return headers;
				}
			}

			function setHeaders(headers) {
				const isFormMode = document.getElementById('headerModeSwitch').checked;
				if (!isFormMode) {
					document.querySelector('textarea[name="headers"]').value =
						headers && Object.keys(headers).length
							? JSON.stringify(headers, null, 2)
							: '';
				} else {
					const tbody = document.querySelector('#headerTable tbody');
					tbody.innerHTML = '';
					if (headers && typeof headers === 'object') {
						for (const k in headers) {
							addHeaderRow(k, headers[k]);
						}
					}
				}
			}

			function toggleHeaderMode() {
				const isFormMode = document.getElementById('headerModeSwitch').checked;
				document.getElementById('header-json-area').style.display = isFormMode
					? 'none'
					: '';
				document.getElementById('header-table-area').style.display = isFormMode
					? ''
					: 'none';
				// 切换时同步内容
				if (isFormMode) {
					// JSON -> 表单
					try {
						const headers = document.querySelector(
							'textarea[name="headers"]'
						).value;
						const obj = headers ? JSON.parse(headers) : {};
						const tbody = document.querySelector('#headerTable tbody');
						tbody.innerHTML = '';
						for (const k in obj) addHeaderRow(k, obj[k]);
					} catch {}
				} else {
					// 表单 -> JSON
					const headers = {};
					document.querySelectorAll('#headerTable tbody tr').forEach((row) => {
						const key = row.querySelector('.header-key').value.trim();
						const value = row.querySelector('.header-value').value;
						if (key) headers[key] = value;
					});
					document.querySelector('textarea[name="headers"]').value =
						Object.keys(headers).length ? JSON.stringify(headers, null, 2) : '';
				}
				saveFormToLocalStorage();
			}

			function saveFormToLocalStorage() {
				const url = document.querySelector('input[name="url"]').value;
				const method = document.querySelector('select[name="method"]').value;
				const count = document.querySelector('input[name="count"]').value;
				const isFormMode = document.getElementById('headerModeSwitch')?.checked;
				let headers = '';
				let headerTable = [];
				if (!isFormMode) {
					headers = document.querySelector('textarea[name="headers"]').value;
				} else {
					document.querySelectorAll('#headerTable tbody tr').forEach((row) => {
						headerTable.push({
							key: row.querySelector('.header-key').value,
							value: row.querySelector('.header-value').value,
						});
					});
				}
				const params = [];
				document.querySelectorAll('#paramsTable tbody tr').forEach((row) => {
					params.push({
						name: row.querySelector('.param-name').value,
						value: row.querySelector('.param-value').value,
						genType: row.querySelector('.param-gen').value,
					});
				});
				const data = {
					url,
					method,
					headers,
					count,
					params,
					headerTable,
					headerMode: isFormMode,
				};
				localStorage.setItem('mockFormCache', JSON.stringify(data));
			}

			function loadFormFromLocalStorage() {
				const data = localStorage.getItem('mockFormCache');
				if (!data) return;
				try {
					const {
						url,
						method,
						headers,
						count,
						params,
						headerTable,
						headerMode,
					} = JSON.parse(data);
					if (url !== undefined)
						document.querySelector('input[name="url"]').value = url;
					if (method !== undefined)
						document.querySelector('select[name="method"]').value = method;
					if (count !== undefined)
						document.querySelector('input[name="count"]').value = count;
					const tbody = document.querySelector('#paramsTable tbody');
					tbody.innerHTML = '';
					if (Array.isArray(params)) {
						params.forEach((p) => addParamRow(p.name, p.value, p.genType));
					}
					// header
					document.getElementById('headerModeSwitch').checked = !!headerMode;
					toggleHeaderMode();
					if (headerMode) {
						const tbody2 = document.querySelector('#headerTable tbody');
						tbody2.innerHTML = '';
						if (Array.isArray(headerTable)) {
							headerTable.forEach((h) => addHeaderRow(h.key, h.value));
						}
					} else {
						document.querySelector('textarea[name="headers"]').value =
							headers || '';
					}
				} catch {}
			}

			function randomString(length = 8) {
				const chars =
					'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
				let result = '';
				for (let i = 0; i < length; i++) {
					result += chars.charAt(Math.floor(Math.random() * chars.length));
				}
				return result;
			}

			function randomMobile() {
				// 简单中国手机号生成
				const prefix = ['13', '14', '15', '16', '17', '18', '19'][
					Math.floor(Math.random() * 7)
				];
				let num = '';
				for (let i = 0; i < 9; i++) num += Math.floor(Math.random() * 10);
				return prefix + num;
			}

			function randomEmail() {
				const name = randomString(6);
				const domains = [
					'qq.com',
					'163.com',
					'gmail.com',
					'outlook.com',
					'test.com',
				];
				const domain = domains[Math.floor(Math.random() * domains.length)];
				return name + '@' + domain;
			}

			function generateMockData() {
				const url = document.querySelector('input[name="url"]').value;
				const method = document.querySelector('select[name="method"]').value;
				let headers = {};
				try {
					headers = getHeaders();
				} catch (e) {
					return;
				}
				const count =
					parseInt(document.querySelector('input[name="count"]').value, 10) ||
					1;
				let incrementCounter = 1;
				const results = [];

				for (let i = 0; i < count; i++) {
					const params = {};
					const rows = document.querySelectorAll('#paramsTable tbody tr');
					rows.forEach((row, idx) => {
						const name = row.querySelector('.param-name').value;
						const genType =
							row.querySelector('.param-gen').options[
								row.querySelector('.param-gen').selectedIndex
							].value;
						let value = row.querySelector('.param-value').value;
						if (genType === 'random') {
							value = randomString();
						} else if (genType === 'increment') {
							value = value + incrementCounter;
							incrementCounter++;
						} else if (genType === 'mobile') {
							value = randomMobile();
						} else if (genType === 'email') {
							value = randomEmail();
						}
						params[name] = value;
					});

					// 生成curl命令
					let curl = `curl '${url}'`;
					// method
					if (method !== 'GET') {
						curl += ` -X ${method}`;
					}
					// headers
					for (const key in headers) {
						curl += ` -H '${key}: ${headers[key]}'`;
					}
					// body/params
					if (method === 'GET') {
						const query = Object.keys(params)
							.map(
								(k) =>
									encodeURIComponent(k) + '=' + encodeURIComponent(params[k])
							)
							.join('&');
						if (query) {
							curl = `curl '${url}${url.includes('?') ? '&' : '?'}${query}'`;
							// 仍然加header
							for (const key in headers) {
								curl += ` -H '${key}: ${headers[key]}'`;
							}
						}
					} else {
						curl += ` --data-raw '${JSON.stringify(params)}'`;
					}
					results.push(curl);
				}
				document.getElementById('result').textContent = results.join('\n\n');
				saveFormToLocalStorage();
			}

			// 更健壮的curl解析，支持URL参数和body参数
			function parseCurl() {
				const curlStr = document.getElementById('curlInput').value;
				if (!curlStr.trim().startsWith('curl')) {
					alert('请输入以 curl 开头的命令');
					return;
				}

				// 1. 解析URL
				let url = '';
				const urlMatch = curlStr.match(/curl\s+['"]([^'"]+)['"]/);
				if (urlMatch) url = urlMatch[1];
				document.querySelector('input[name="url"]').value = url;

				// 2. 默认为GET，有data参数时自动设为POST
				let method = 'GET';
				const methodMatch = curlStr.match(/-X\s+(\w+)/i);
				if (methodMatch) {
					method = methodMatch[1].toUpperCase();
				} else if (/--data-raw\b|-d\b|--data\b/.test(curlStr)) {
					method = 'POST';
				}
				document.querySelector('select[name="method"]').value = method;

				// 3. 解析所有headers（包括 -H 和 -b）
				let headers = {};

				// 3.1 处理 -H 参数
				const headerRegex = /-H\s+['"]([^'"]+)['"]/g;
				let m;
				while ((m = headerRegex.exec(curlStr)) !== null) {
					const [key, value] = m[1].split(/:\s*/);
					headers[key] = value;
				}

				// 3.2 处理 -b 参数（cookie）
				const cookieMatch = curlStr.match(/-b\s+['"]([^'"]+)['"]/);
				if (cookieMatch) {
					headers['Cookie'] = cookieMatch[1];
				}

				// 修复：根据当前header模式分别回显
				const isFormMode = document.getElementById('headerModeSwitch').checked;
				if (!isFormMode) {
					document.querySelector('textarea[name="headers"]').value =
						Object.keys(headers).length ? JSON.stringify(headers, null, 2) : '';
				} else {
					const tbody = document.querySelector('#headerTable tbody');
					tbody.innerHTML = '';
					for (const k in headers) {
						addHeaderRow(k, headers[k]);
					}
				}

				// 4. 解析参数（先清空参数表）
				document.querySelector('#paramsTable tbody').innerHTML = '';

				// 4.1 解析URL中的查询参数
				let queryStr = '';
				try {
					const urlObj = new URL(url, 'http://dummy.com');
					queryStr = urlObj.search ? urlObj.search.substring(1) : '';
				} catch {}
				if (queryStr) {
					queryStr.split('&').forEach((pair) => {
						if (pair) {
							const [k, v] = pair.split('=');
							addParamRow(
								decodeURIComponent(k),
								decodeURIComponent(v || ''),
								'manual'
							);
						}
					});
				}

				// 4.2 解析body参数（支持 --data/-d/--data-raw，兼容 $'...' 格式和多行）
				// 调整正则，兼容 $'...'、'...'、"..."，且允许 $'...' 前有空格
				const dataMatch = curlStr.match(
					/(?:--data-raw|--data|-d)\s+(\$?)(['"])\s*([\s\S]*?)\2/
				);
				if (dataMatch) {
					let dataStr = dataMatch[3];
					// 处理 $'...' 格式的转义字符
					if (dataMatch[1] === '$') {
						dataStr = dataStr
							.replace(/\\n/g, '\n')
							.replace(/\\r/g, '\r')
							.replace(/\\t/g, '\t');
					}

					// 处理多行JSON，移除空白并标准化
					let tryJsonStr = dataStr.trim();
					if (tryJsonStr.startsWith('{') && tryJsonStr.endsWith('}')) {
						// 只去除每行前后空白，不合并为一行
						tryJsonStr = tryJsonStr
							.split('\n')
							.map((line) => line.trim())
							.filter((line) => line.length > 0)
							.join('\n');
						// 尝试直接解析
						try {
							const dataObj = JSON.parse(tryJsonStr);
							for (const k in dataObj) {
								addParamRow(k, dataObj[k], 'manual');
							}
							return;
						} catch (e) {
							// 如果失败，尝试自动补全缺失的逗号
							tryJsonStr = tryJsonStr.replace(/([}\]"'])\s*(?=\n)/g, '$1,');
							try {
								// 关键修正：先用空格替换换行符，若失败再尝试去除所有逗号后合并为一行
								const dataObj = JSON.parse(tryJsonStr.replace(/\n/g, ' '));
								for (const k in dataObj) {
									addParamRow(k, dataObj[k], 'manual');
								}
								return;
							} catch (e2) {
								// 兜底：去除所有换行和多余逗号再尝试
								try {
									const compactStr = tryJsonStr
										.replace(/\n/g, '')
										.replace(/,}/g, '}');
									const dataObj = JSON.parse(compactStr);
									for (const k in dataObj) {
										addParamRow(k, dataObj[k], 'manual');
									}
								} catch (e3) {
									console.error('JSON解析失败:', e3);
								}
							}
						}
					}
				}
				setHeaders(headers);
				saveFormToLocalStorage();
			}

			// 新增：执行请求
			async function executeRequest() {
				const url = document.querySelector('input[name="url"]').value;
				const method = document.querySelector('select[name="method"]').value;
				let headers = {};
				try {
					headers = getHeaders();
				} catch (e) {
					return;
				}
				const count =
					parseInt(document.querySelector('input[name="count"]').value, 10) ||
					1;
				let incrementCounter = 1;
				const results = [];
				for (let i = 0; i < count; i++) {
					const params = {};
					const rows = document.querySelectorAll('#paramsTable tbody tr');
					rows.forEach((row) => {
						const name = row.querySelector('.param-name').value;
						const genType =
							row.querySelector('.param-gen').options[
								row.querySelector('.param-gen').selectedIndex
							].value;
						let value = row.querySelector('.param-value').value;
						if (genType === 'random') {
							value = randomString();
						} else if (genType === 'increment') {
							value = value + incrementCounter;
							incrementCounter++;
						} else if (genType === 'mobile') {
							value = randomMobile();
						} else if (genType === 'email') {
							value = randomEmail();
						}
						params[name] = value;
					});

					let fetchUrl = url;
					let fetchOptions = { method, headers: { ...headers } };

					if (method === 'GET') {
						const query = Object.keys(params)
							.map(
								(k) =>
									encodeURIComponent(k) + '=' + encodeURIComponent(params[k])
							)
							.join('&');
						if (query) {
							fetchUrl += (fetchUrl.includes('?') ? '&' : '?') + query;
						}
					} else {
						fetchOptions.body = JSON.stringify(params);
						if (
							!fetchOptions.headers['Content-Type'] &&
							!fetchOptions.headers['content-type']
						) {
							fetchOptions.headers['Content-Type'] = 'application/json';
						}
					}
					results.push({ fetchUrl, fetchOptions });
				}

				const responses = [];
				for (let i = 0; i < results.length; i++) {
					try {
						const resp = await fetch(
							results[i].fetchUrl,
							results[i].fetchOptions
						);
						const text = await resp.text();
						responses.push({
							request: results[i].fetchUrl,
							status: resp.status,
							response: text,
						});
					} catch (err) {
						responses.push({
							request: results[i].fetchUrl,
							status: 'error',
							response: '请求失败：' + err,
						});
					}
				}
				document.getElementById('result').textContent = JSON.stringify(
					responses,
					null,
					2
				);
				saveFormToLocalStorage();
			}

			// 监听表单输入变化自动保存
			document.addEventListener('DOMContentLoaded', function () {
				loadFormFromLocalStorage();
				document
					.querySelector('input[name="url"]')
					.addEventListener('input', saveFormToLocalStorage);
				document
					.querySelector('select[name="method"]')
					.addEventListener('change', saveFormToLocalStorage);
				document
					.querySelector('textarea[name="headers"]')
					.addEventListener('input', saveFormToLocalStorage);
				document
					.querySelector('input[name="count"]')
					.addEventListener('input', saveFormToLocalStorage);
			});

			// 初始化添加一行参数（如果本地没有缓存）
			if (!localStorage.getItem('mockFormCache')) {
				addParamRow();
			}
		</script>
	</body>
</html>
